package com.oneguy.client;

import com.oneguy.client.log.Log;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;

/**
 * Created by ZuoShu on 17/9/14.
 */
public class TcpConnection implements Runnable {
    private static final String TAG = TcpConnection.class.getSimpleName();
    private static final int WAIT_PEER_PERIOD = 1000;
    private static final int SOCKET_TIMEOUT_MILLIS = 30 * 1000;
    private static final int KEEP_ALIVE_TIMEOUT = 5 * 60 * 1000;
    private static final int MAX_PACKET = 0XFFFF;
    private static final int DEFAULT_CONNECT_TIMEOUT = 5 * 1000;
    private static final int SOCKET_SEND_BUFFER_SIZE = 8 * 1024;
    private static final int SOCKET_RECEIVE_BUFFER_SIZE = 0xFFFF;
    private static final int WAIT_PEER_INTERNAL = 5 * 1000;
    private static final int WAIT_PEER_MAX_TIME = 12;
    private boolean mIsRunning;
    private Socket socket;
    private DataOutputStream mOutput;
    private DataInputStream mInput;
    private Thread mThread;
    private TcpConnectionListener listener;
    private TimeoutWatchdog mTimeoutWatchdog;

    public TcpConnection() {
        mIsRunning = false;
        mThread = null;
        mTimeoutWatchdog = new TimeoutWatchdog(KEEP_ALIVE_TIMEOUT,
                new Runnable() {
                    public void run() {
                        Log.w(TAG,
                                "connection timeout,timeout="
                                        + KEEP_ALIVE_TIMEOUT
                                        + " remoteName="
//                                        + (mDestPeer == null ? null : mDestPeer
//                                        .getInstanceName())
                                        + " remote="
                                        + (socket == null ? null : socket
                                        .getRemoteSocketAddress()));
                        disconnect();
                    }
                });
    }

    public TcpConnectionListener getListener() {
        return listener;
    }

    public void setListener(TcpConnectionListener mListener) {
        this.listener = mListener;
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    public void connect(Socket socket) throws ConnectionException {
        try {
            if (socket == null) {
                throw new ConnectionException(
                        "error connect socket to message peer!");
            }
            this.socket = socket;
            // wait and read, watchdog take cares of timeout
            // socket.setSoTimeout(0);
            this.socket.setReuseAddress(true);
            this.socket.setSendBufferSize(SOCKET_SEND_BUFFER_SIZE);
            this.socket.setReceiveBufferSize(SOCKET_RECEIVE_BUFFER_SIZE);
            mInput = new DataInputStream(new BufferedInputStream(
                    this.socket.getInputStream(), SOCKET_RECEIVE_BUFFER_SIZE));
            mOutput = new DataOutputStream(new BufferedOutputStream(
                    this.socket.getOutputStream(), SOCKET_SEND_BUFFER_SIZE / 2));
        } catch (IOException e) {
            e.printStackTrace();
            throw new ConnectionException("error connect socket!", e);
        }
        if (listener != null) {
            listener.onConnect(this);
        }
        startConnectionThread();
        mTimeoutWatchdog.start();
    }

    public void connect(String ip, int port) throws ConnectionException {
        try {
            Socket s = new Socket();
            SocketAddress address = new InetSocketAddress(ip, port);
            s.connect(address, DEFAULT_CONNECT_TIMEOUT);
            connect(s);
        } catch (UnknownHostException e) {
            e.printStackTrace();
            throw new ConnectionException("error connect socket!", e);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ConnectionException("error connect socket!", e);
        }
    }

    public void sendMessage(byte[] data) throws IOException {
        if (data.length >= MAX_PACKET) {
            throw new IOException("packet too big:" + data.length);
        }
        byte[] finalData = new byte[data.length + 4];
        finalData[0] = 0x05;
        finalData[1] = 0x00;
        finalData[2] = (byte) (data.length & 0xff);
        finalData[3] = (byte) ((data.length & 0xff00) >> 8);
        System.arraycopy(data, 0, finalData, 4, data.length);
        mOutput.write(finalData);
        mOutput.flush();
        mTimeoutWatchdog.extend();
        if (listener != null) {
            listener.onSend(this, finalData);
        }
    }

    public void disconnect() {
        mIsRunning = false;
        mTimeoutWatchdog.stop();
        if (mThread != null) {
            mThread.interrupt();
            mThread = null;
        }
        try {
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to close the message peer session", e);
        }
        if (listener != null) {
            listener.onDisconnect(this);
        }
    }

    private byte[] readPacket() throws IOException {
        int first4Bytes = mInput.readInt();
        byte byte4 = (byte) (first4Bytes & 0xff);
        byte byte3 = (byte) ((first4Bytes & 0xff00) >> 8);
        byte byte2 = (byte) ((first4Bytes & 0xff0000) >> 16);
        byte byte1 = (byte) ((first4Bytes & 0xff000000) >> 24);

        int length = ((first4Bytes & 0xff) << 8) | ((first4Bytes & 0xff00) >> 8);
        Log.d(TAG, "header:" + Utils.printHexBinary(new byte[]{byte1, byte2, byte3, byte4}) + " length:" + length);

        byte[] data = new byte[length];
        mInput.readFully(data);
        return data;
    }

    public void run() {
        while (mIsRunning) {
            try {
                byte[] packet = readPacket();
                mTimeoutWatchdog.extend();
                if (listener != null) {
                    listener.onReceive(this, packet);
                }
            } catch (IOException e) {
                if (mIsRunning) {
                    if (listener != null) {
                        listener.onDisconnect(this);
                    }
                    break;
                }
            }
        }
        try {
//            Log.d(TAG,
//                    "TcpConnection stop running!remote="
//                            + (socket == null ? null : socket
//                            .getRemoteSocketAddress()));
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            Log.e(TAG, "Error closing TCP");
            if (listener != null) {
                listener.onException(this, e);
            }
        }
    }

    private void startConnectionThread() {
        mIsRunning = true;
        mThread = new Thread(this, "TcpConnection");
        mThread.start();
    }
}
